#pragma once
#include "Socket.hpp"
#include "connItem.hpp"
#include "Protocol.hpp"

#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>

#include <thread>
#include <unordered_map>

#define BUFFER_LENGTH 1024
#define EVENT_ADD 1
#define EVENT_DEL 2
#define EVENT_MOD 3

// 将一个函数设置为非阻塞
bool SetNonBlock(int sockfd)
{
    if (sockfd < 0)
        return false;

    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags < 0)
        return false;

    int n = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    return n > 0;
}

class tcpServer
{
    static const uint16_t defaultPort = 2025;
    static const int fdnums = 4096 * 2;

public:
    tcpServer()
        : _epfd(-1) {}
    ~tcpServer()
    {
        // 清理所有连接
        for (auto &pair : _connlist)
        {
            close(pair.first);
            delete pair.second;
        }
        _connlist.clear();

        delete[] _revents;
    }

    // 增加监听的端口
    void addListenPort(int port)
    {
        // 服务器初始化,在creat中已经设置为非阻塞了
        int listensock = mySocket::creatSockfd();
        // 设置端口复用，保证服务器退出后能够快速bind
        int opt = 1;
        setsockopt(listensock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

        mySocket::Bind(listensock, port);
        mySocket::Listen(listensock, gbacklog);

        // 将listensock 与 对应端口添加到哈希表中
        _listensock_fds.insert(std::make_pair(listensock, port));
    }

    // 初始化到监听即可
    void init()
    {
        // epoll初始化，返回事件集合初始化
        _epfd = epoll_create(1);
        _revents = new struct epoll_event[fdnums];

        // 遍历哈希表，将所有的listenfd 与 port进行 关心
        if (_listensock_fds.empty())
        {
            std::cerr << "没有设置监听端口!" << std::endl;
            exit(-1);
        }

        for (auto &kv : _listensock_fds)
        {
            AddConnList(kv.first, EPOLLIN | EPOLLET, [this](connItem *conn)
                        { this->Accepter(conn); }, nullptr, nullptr);
        }
    }

    // 事件派发器
    void Dispatcher()
    {
        printf("Dispatcher start\n");

        while (true)
        {
            int n = epoll_wait(_epfd, _revents, fdnums, -1);

            // 遍历就绪队列，epoll只会返回真的就绪的事件。不会返回无效事件，减少遍历
            for (int i = 0; i < n; i++)
            {
                int connfd = _revents[i].data.fd;
                uint32_t events = _revents[i].events;

                // 哈希表中该连接没有删除
                if (_connlist.count(connfd))
                {
                    // 不要使用if    elseif     else，因为同一个事件有可能读写事件都就绪了
                    if ((events & EPOLLIN) && _connlist[connfd]->_recver != nullptr) // 回调执行fd对应读事件
                        _connlist[connfd]->_recver(_connlist[connfd]);

                    if ((events & EPOLLOUT) && _connlist[connfd]->_sender != nullptr) // 回调执行fd对应写事件
                        _connlist[connfd]->_sender(_connlist[connfd]);
                }
            }
        }
        printf("Dispatcher over\n");
    }

private:
    // 初始化连接信息和方法，注册到epoll关心列表中，并放入连接信息哈希表中
    void AddConnList(int sockfd, uint32_t event, func_t reader, func_t sender, func_t execpter)
    {
        // ET模式下，将fd设置为非阻塞
        int n = SetNonBlock(sockfd);
        if (n < 0)
        {
            printf("SetNonBlock 失败!\n");
            exit(-1);
        }

        // 1.构建连接信息,并注册方法
        connItem *conn = new connItem(sockfd, this);
        conn->Register(reader, sender, execpter);

        // 2.让epoll关心该事件
        SetEvent(sockfd, event, EVENT_ADD);
        // 3.放入连接信息哈希表中
        _connlist.insert(std::make_pair(sockfd, conn));
    }

    void SetEvent(int sockfd, uint32_t event, int flag)
    {
        struct epoll_event ev;
        ev.data.fd = sockfd;
        ev.events = event;

        if (flag == EVENT_ADD) // 新增
            epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
        else if (flag == EVENT_MOD) // 修改
            epoll_ctl(_epfd, EPOLL_CTL_MOD, sockfd, &ev);
        else if (flag == EVENT_DEL) // 删除
            epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, 0);
    }

    // listenfd 触发EOILLIN执行
    void Accepter(connItem *conn)
    {
        // 1.获取新连接的fd,注意ET模式下，需要死循环一次性将所有数据读取完毕。否则会出现问题
        //printf("Accepter start\n");
        while (true)
        {
            std::string clientip;
            uint16_t clientport;

            int err = 0;
            int clientsock = mySocket::Accept(conn->_sockfd, clientip, clientport, err);

            // 2.构建新连接的信息表,让epoll关心该事件同时并通过哈希表进行管理。需要使用lambda进行处理类内回调函数
            if (clientsock > 0)
            {
                AddConnList(clientsock, EPOLLIN | EPOLLET, [this](connItem *conn)
                            { this->Reader(conn); }, [this](connItem *conn)
                            { this->Sender(conn); }, nullptr); // 这里暂时不处理异常事件
                //printf("Get a new link, info [%s:%d] clientsock[%d]\n", clientip.data(), clientport, clientsock);
            }
            else
            {
                // 处理EAGAIN等异常信号
                if (err == EAGAIN || err == EWOULDBLOCK)
                {
                    // 没有连接了
                    // printf("DEBUG Accepter EAGAIN 没有更多连接，此次获取连接结束\n");
                    break;
                }
                else if (err == EINTR)
                {
                    // printf("DEBUG Accepter EINTR 还有更多连接需要处理\n");
                    continue;
                }
                else
                {
                    // printf("ERRNO Accepter 建立连接失败\n");
                    break;
                }
            }
        }
        //printf("Accepter over\n");
    }

    // clientfd触发EOILLIN执行
    void Reader(connItem *conn)
    {
        char buffer[1024];
        int maxloop = 100;
        while (maxloop--)
        {
            int count = recv(conn->_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (count > 0)
            {
                buffer[count] = 0;
                conn->_inbuffer += buffer;
            }
            else if (count == 0)
            {
                //printf("DEBUG Reader recv over\n");
                RemoveConn(conn->_sockfd);
                return;
            }
            else
            {
                // 同理需要处理EAGAIN和EINTER
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 无数据可读
                    // printf("DEBUG Reader EAGAIN\n");
                    break;
                }
                else if (errno == EINTR)
                {
                    // printf("DEBUG  Reader EINTR\n");
                    continue;
                }
                else
                {
                    // printf("ERRNO Reader 建立连接失败\n");
                    //  关闭套接字和取消epoll关心，然后退出
                    RemoveConn(conn->_sockfd);
                    return;
                }
            }
        }

#if 1
        // 对数据进行处理，这里直接原数据返回
        std::cout << conn->_inbuffer << std::endl;
        conn->_outbuffer += conn->_inbuffer;

        conn->_inbuffer.clear();
        // std::cout << conn->_outbuffer << std::endl;

        // 需要尝试发送数据，如果服务端不主动发送数据，后续写事件都无法触发,因为写事件都是在读事件之后才会触发的！
        // 当然，我们也能在这里让epoll关心写事件就绪，让其自己触发。不过效率没有尝试发送这种效率高
        conn->_sender(conn);
#else
        _service(conn);
#endif
    }

    // clientfd触发EOILLOUT执行
    void Sender(connItem *conn)
    {
        int maxloop = 100;
        while (maxloop--)
        {
            int count = send(conn->_sockfd, conn->_outbuffer.c_str(), conn->_outbuffer.size(), 0);

            if (count > 0)
            {
                // 清空发送的数据
                conn->_outbuffer.erase(0, count);
                // 数据发送完毕
                if (conn->_outbuffer.empty())
                {
                    // 此时不可以直接更改事件的关系，因为数据可能还在内核，没有发送到网络
                    // printf("DEBUG Senderr send over\n");
                    break;
                }
            }
            else if (count == 0)
            {
                // 没有数据发送
                RemoveConn(conn->_sockfd);
                return;
            }
            else
            {
                // 同理需要处理EAGAIN和EINTER
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    // 关闭套接字和取消epoll关心，然后退出
                    RemoveConn(conn->_sockfd);
                    return;
                }
            }
        }

        // 到这里，事件结束了，数据才是真的发送出去了通重新关心该事件的读写

        if (conn->_outbuffer.empty())
            SetEvent(conn->_sockfd, EPOLLIN | EPOLLET, EVENT_MOD);
        else
            SetEvent(conn->_sockfd, EPOLLET | EPOLLIN | EPOLLOUT, EVENT_MOD);
    }

private:
    void RemoveConn(int sockfd)
    {
        auto it = _connlist.find(sockfd);
        if (it != _connlist.end())
        {
            // 从epoll中删除
            SetEvent(sockfd, 0, EVENT_DEL);
            // 关闭socket
            close(sockfd);
            // 释放内存
            delete it->second;
            // 从哈希表删除
            _connlist.erase(it);
            // printf("连接关闭: fd=%d, 剩余连接数: %zu\n", sockfd, _connlist.size());
        }
    }

private:
    // 如果要同时监听多个端口，就需要维护每一个sockfd与对应端口的信息
    std::unordered_map<int, int> _listensock_fds;

    int _epfd;             // epollfd
    epoll_event *_revents; // 返回事件的列表

    std::unordered_map<int, connItem *> _connlist{}; // 用于快速查找fd和对于的连接结构体conn
    func_t _service = HttpResponse; // 协议定制函数
};